"""
Custom Text widgets.
Written By: Daniel Ortiz
"""
import wx, markdown
import wx.richtext as rt
from difflib import SequenceMatcher
from handler.html2text import html2text

tags = ['<p>', '<hr>', '<head>', '<body>',  '<div>', '<html>', '<var>', '<ul>', '<u>', '<tt>', '<title>', '<thead>', 
        '<th>', '<tfoot>', '<textarea>', '<td>', '<tbody>', '<table>', '<applet>', '<acronym>', '<abbr>', '<a>',
        '<sup>', '<sub>', '<style>', '<strong>', '<span>', '<small>', '<select>', '<script>', '<samp>', '<q>', '<pre>', '<param>',
        '<option>','<optgroup>', '<ol>', '<object>', '<noscript>', '<noframes>', '<meta>', '<map>', '<link>', '<li>', 
        '<legend>', '<label>', '<kbd>', '<ins>', '<input>', '<img>', '<iframe>', '<i>' , '<h1>', '<h2>', '<h3>', '<h4>', '<h5>', '<h6>',
        '<frameset>', '<frame>', '<form>', '<font>', '<fielset>', '<em>', '<dt>', '<dl>', '<dfn>', '<del>', '<dd>', '<colgroup>', '<col>',
        '<code>', '<cite>', '<caption>', '<button>', '<br>', '<blockquote>', '<big>', '<bdo>', '<base>', '<b>', '<area>']

def hasTag(text):
    """Searches for all html tags, returns true if it finds one"""
    body = text.lower()
    for t in tags:
        if t in body:
            return True
    return False

class TextStateMixin(object):
    def __init__(self):
        self._Variables()
        self._BindEvents()
        
    def _Variables(self):
        """Initiate class wide variables"""
        self.modification = False
        self.previous_string = ""
        self.current_string = ""
        self.add_action = 'ADD'
        self.delete_action = 'DELETE'
        
    def _BindEvents(self):
        """Bind the class wide events"""
        self.Bind(wx.EVT_TEXT, self._OnText)
        self.Bind(wx.EVT_KEY_DOWN, self._OnKeyDown)
        
    def _OnText(self, event):
        """Handle the text event and log history"""
        self.modification = True
        self.previous_string = self.current_string
        self.current_string = self.GetValue()
        str, range, action = self._FindDifference()
        history_buffer = self.GetTopLevelParent().history_buffer
        history_buffer.Log(str, range, action, self._ChangeState)
        
    def _FindDifference(self):
        """Finds the difference between the current and previous string. Depends on incremental changes"""
        s = SequenceMatcher(lambda x: x == " ", self.previous_string, self.current_string)
        block = s.get_matching_blocks()
        first_index = 0
        pblock = block[len(block) - 1]
        if len(self.current_string) > len(self.previous_string):
            for b in block:
                if b[0] < b[1]:
                    first_index = b[0]
                    break
            str = self.current_string[first_index: first_index + pblock[1] - pblock[0]]
            range = (first_index, first_index + (pblock[1] - pblock[0]))
            action = self.add_action
        else:
            for b in block:
                if b[0] > b[1]:
                    first_index = b[1]
                    break
            str = self.previous_string[first_index: first_index + pblock[0] - pblock[1]]
            range = (first_index, first_index + (pblock[0] - pblock[1]))
            action = self.delete_action
        return str, range, action
    
    def _OnKeyDown(self, event):
        """When a key is pressed, parse for a potential veto"""
        if event.CmdDown():
            hbuffer = self.GetTopLevelParent().history_buffer
            if event.GetKeyCode() == 90:
                hbuffer.Undo()
            elif event.GetKeyCode() == 89:
                hbuffer.Redo()
        else:
            event.Skip()
    
    def _ChangeState(self, char, pos, action, state):
        """Change to a different state"""
        if action == self.add_action:
            if state == "UNDO":
                self.Remove(pos[0], pos[1])
            else:
                self.SetInsertionPoint(pos[0])
                self.WriteText(char)
        else:
            if state == 'UNDO':
                self.SetInsertionPoint(pos[0])
                self.WriteText(char)
            else:
                self.Remove(pos[0], pos[1])
                
        self.current_string = self.GetValue()
        

class MailText(wx.TextCtrl):
    def __init__(self, parent):
        self.parent = parent
        wx.TextCtrl.__init__(self, parent)
        self.AddToBundle()
        self.BindEvents()
    
    def AddToBundle(self):
        """Add the current object to the bundle"""
        bundle = self.GetTopLevelParent().mail_text_bundle
        bundle.Add(self, self.GetValue)
        
    def BindEvents(self):
        """Bind class-wide events"""
        pass
#        self.Bind(wx.EVT_SET_FOCUS, self.OnSetFocus)
#        self.Bind(wx.EVT_KILL_FOCUS, self.OnLoseFocus)
#        
#    def OnLoseFocus(self, event):
#        """Losing the focus of the window"""
#        self.__EnableMenuBar(False)
#        
#    def __EnableMenuBar(self, enable):
#        parent = self.GetTopLevelParent()
#        ids = parent.idedit
#        menubar = parent.GetMenuBar()
#        if enable:
#            menubar.Enable(ids["Cut"], enable)
#            menubar.Enable(ids["Paste"], enable)
#            menubar.Enable(ids["Copy"], enable)
#            menubar.Enable(ids["Redo"], not enable)
#            menubar.Enable(ids["Undo"], not enable)
#            menubar.Enable(ids["SelectAll"], enable)
#        else:
#            for edit, id in ids.iteritems():
#                menubar.Enable(id, False)
#        
#    def OnSetFocus(self, parent):
#        """Setting the focus of the window"""
#        self.__EnableMenuBar(True)
        
    def OnFocus(self, event):
        """Tell the bundle about the focus event"""
        pass
        
class MailSearchText(wx.SearchCtrl, TextStateMixin):
    def __init__(self, parent):
        self.parent = parent
        size = (480, -1)
        wx.SearchCtrl.__init__(self, parent, size = size, style = wx.TE_PROCESS_ENTER)
        TextStateMixin.__init__(self)
        self.AddToBundle()
        self.CreateMenu()
        self.BindEvents()
    
    def AddToBundle(self):
        """Add the current object to the bundle"""
        bundle = self.GetTopLevelParent().mail_text_bundle
        bundle.Add(self, self.GetValue)
        
    def BindEvents(self):
        """Bind class-wide events"""
        self.Bind(wx.EVT_TEXT_ENTER, self.OnEnter)
        self.Bind(wx.EVT_SEARCHCTRL_CANCEL_BTN, self.OnCancel)
    
    def CreateMenu(self):
        """Create the search menu to add dynamic control to the search process"""
        header = wx.Menu()
        self.all = header.AppendCheckItem(-1, "All")
        self.body = header.AppendCheckItem(-1, "Body")
        self.to = header.AppendCheckItem(-1, "To")
        self.from_who = header.AppendCheckItem(-1, "From")
        self.subject = header.AppendCheckItem(-1, "Subject")
        self.SetMenu(header)
        self.all.Check(True)

    def GetMenuSelection(self):
        """Return the selection in a dictionary"""
        ret = {}
        ret['All'] = self.all.IsChecked()
        ret['Body'] = self.body.IsChecked()
        ret['From'] = self.from_who.IsChecked()
        ret['To'] = self.to.IsChecked()
        ret['Subject'] = self.subject.IsChecked()
        return ret 
    
    def OnEnter(self, event):
        """Capture an enter and search the mail for the query"""
        d = self.GetTopLevelParent().database_handler
        sel = self.GetMenuSelection()
        if self.IsCancelButtonVisible():
            d.EndSearch()
        f = d.Search(event.GetString(), sel)
        if not f:
            wx.MessageBox("Please check one of the controls in the search menu.", "No controls",  wx.OK)
        elif f == 2:
            wx.MessageBox("Please setup a mailbox by pressing New before searching.", "No Mailbox", wx.OK)
        else:
            self.ShowCancelButton(True)
        
    def OnCancel(self, event):
        """Cancel the current search"""
        d = self.GetTopLevelParent().database_handler
        d.EndSearch()
        self.ShowCancelButton(False)
        
class RichMailText(rt.RichTextCtrl):
    def __init__(self, parent):
        self.parent = parent
        rt.RichTextCtrl.__init__(self, parent, style = wx.VSCROLL | wx.HSCROLL | wx.SUNKEN_BORDER)
        self.Variables()
        self.BindEvents()
    
    def Variables(self):
        """Initiate class-wide variables"""
        self.resize = False
        self.modified = False
    
    def BindEvents(self):
        """Bind the class wide events"""
        self.Bind(wx.EVT_SIZE, self.OnSize)
        self.Bind(wx.EVT_IDLE, self.OnIdle)
#        self.Bind(wx.EVT_KILL_FOCUS, self.OnLoseFocus)
#        self.Bind(wx.EVT_SET_FOCUS, self.OnSetFocus)
#        
#    def OnLoseFocus(self, event):
#        """Losing the focus of the window"""
#        self.__EnableMenuBar(False)
#        
#    def __EnableMenuBar(self, enable):
#        parent = self.GetTopLevelParent()
#        ids = parent.idedit
#        menubar = parent.GetMenuBar()
#        for id in ids.iteritems():
#            menubar.Enable(id, enable)
#        
#    def OnSetFocus(self, parent):
#        """Setting the focus of the window"""
#        self.__EnableMenuBar(True)
        
    def OnSize(self, event):
        """Resize the window"""
        self.resize = True
        event.Skip()
        
    def OnIdle(self, event):
        """Resize the window when idle"""
        if self.resize:
            self.resize = False
            
    def Add(self, body):
        """Add the text"""
        self.modified = True
        if hasTag(body):
            text = html2text(body)
        else:
            text = body
        self.SetValue(text)
        
    def GetData(self):
        """Return the current value of the richtextctrl"""
        return self.GetValue()
    
    def OnCut(self, event):
        """Cut the selection, and add it to the global clipboard"""
        self.Cut()

    def OnCopy(self, event):
        """Copy the most current selection from the clipboard"""
        self.Copy()

    def OnPaste(self, event):
        """Paste the most current selection from the clipboard"""
        self.Paste()

    def OnUndo(self, event):
        """Undo the last action in the chat window"""
        self.Undo()

    def OnRedo(self, event):
        """Redo the last action in the chat window"""
        self.Redo()

    def OnAlignLeft(self, event):
        """Realign the selection to the left"""
        self.ApplyAlignmentToSelection(rt.TEXT_ALIGNMENT_LEFT)

    def OnAlignRight(self, event):
        """Realign the selection to the right"""
        self.ApplyAlignmentToSelection(rt.TEXT_ALIGNMENT_RIGHT)

    def OnAlignCenter(self, event):
        """Realign the selection to the center"""
        self.ApplyAlignmentToSelection(rt.TEXT_ALIGNMENT_CENTRE)

    def AsHTML(self):
        """Return the text as html"""
        return markdown.markdown(self.GetValue())
    
    def OnFont(self, event):
        """Change the font for the selection"""
        attr = rt.RichTextAttr()
        ip = self.GetInsertionPoint()
        if self.GetStyle(ip, attr):
            r = rt.RichTextRange(ip, ip)
            if self.HasSelection():
                r = self.GetSelectionRange()

            dialog = wx.FontDialog(None, wx.FontData())
            data = None
            if dialog.ShowModal() == wx.ID_OK:
                data = dialog.GetFontData()
                font = data.GetChosenFont()
            dialog.Destroy()
            if data:
                attr.SetFont(font)
                self.SetStyle(r, attr)
                
    def OnColor(self, event):
        """Change the hue of the selection"""
        attr = rt.RichTextAttr()
        ip = self.GetInsertionPoint()
        if self.GetStyle(ip, attr):
            r = rt.RichTextRange(ip, ip)
            if self.HasSelection():
                r = self.GetSelectionRange()
                
            dialog = wx.ColourDialog(None)
            dialog.GetColourData().SetChooseFull(True)
            data = None
            if dialog.ShowModal() == wx.ID_OK:
                data = dialog.GetColourData()
                colour = data.GetColour()
            dialog.Destroy()
            if data:
                attr.SetTextColour(colour)
                self.SetStyle(r, attr)
                
    def OnBold(self, event):
        """Make the selection bold"""
        self.ApplyBoldToSelection()

    def OnUnderline(self, event):
        """Underline the current selection"""
        self.ApplyUnderlineToSelection()
        
    def OnItalic(self, event):
        """Italicize the current selection"""
        self.ApplyItalicToSelection()